/**
 * 20211008 created by jordia
 */
import {BufferGeometry, Float32BufferAttribute, Vector3} from "three";

/**
 * three.js-style CapsuleBufferGeometry
 */
export default class CapsuleBufferGeometry extends BufferGeometry{
    constructor(radius=1,height=1,widthSegments=32,endSegments=16,bodySegments=1,phiStart=0,phiLength=Math.PI*2){
        super();
        this.type = 'CapsuleBufferGeometry';
        radius=radius||1;
        height=height||1;
        widthSegments=Math.max(Math.floor(widthSegments),3);
        endSegments=Math.max(Math.floor(endSegments),1);
        bodySegments=Math.max(Math.floor(bodySegments),1);
        this.parameters = {
            radius: radius,
            height:height,
            widthSegments: widthSegments,
            endSegments: endSegments,
            bodySegments: bodySegments,
            phiStart: phiStart,
            phiLength: phiLength,
        };

        let index = 0;
        const grid = [];

        const vertex = new Vector3();
        const normal = new Vector3();

        const indices = [];
        const vertices = [];
        const normals = [];
        const uvs = [];
        const rr=radius/(height+radius*2);
        const rh=height/(height+radius*2);
        const total=endSegments*2+bodySegments+2;

        //top end
        let thetaStart=0;
        let thetaLength=Math.PI*0.5;
        for ( let iy = 0; iy <= endSegments; iy ++ ) {
            const verticesRow = [];
            const v = iy / endSegments;
            for ( let ix = 0; ix <= widthSegments; ix ++ ) {
                const u = ix / widthSegments;
                vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
                vertex.y = radius * Math.cos( thetaStart + v * thetaLength );
                vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
                vertices.push( vertex.x, vertex.y+height*0.5, vertex.z );
                normal.copy( vertex ).normalize();
                normals.push( normal.x, normal.y, normal.z );
                uvs.push( u , (2- v)*rr+rh );
                verticesRow.push( index ++ );
            }
            grid.push( verticesRow );
        }

        //middle body
        thetaStart=Math.PI*0.5;
        thetaLength=0;
        for ( let iy = 0; iy <= bodySegments; iy ++ ) {
            const verticesRow = [];
            const v = iy / bodySegments;
            for ( let ix = 0; ix <= widthSegments; ix ++ ) {
                const u = ix / widthSegments;
                vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
                vertex.y =(0.5-v)*height;
                vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
                vertices.push( vertex.x, vertex.y, vertex.z );
                normal.copy( vertex ).y=0;
                normal.normalize();
                normals.push( normal.x, normal.y, normal.z );
                uvs.push( u , (1-v)*rh+rr );
                verticesRow.push( index ++ );
            }
            grid.push( verticesRow );
        }

        //bottom end
        thetaStart=Math.PI*0.5;
        thetaLength=Math.PI*0.5;
        for ( let iy = 0; iy <= endSegments; iy ++ ) {
            const verticesRow = [];
            const v = iy / endSegments;
            for ( let ix = 0; ix <= widthSegments; ix ++ ) {
                const u = ix / widthSegments;
                vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
                vertex.y = radius * Math.cos( thetaStart + v * thetaLength );
                vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
                vertices.push( vertex.x, vertex.y-height*0.5, vertex.z );
                normal.copy( vertex ).normalize();
                normals.push( normal.x, normal.y, normal.z );
                uvs.push( u , (1 - v)*rr );
                verticesRow.push( index ++ );
            }
            grid.push( verticesRow );
        }

        for ( let iy = 0; iy < total; iy ++ ) {
            for ( let ix = 0; ix < widthSegments; ix ++ ) {
                const a = grid[ iy ][ ix + 1 ];
                const b = grid[ iy ][ ix ];
                const c = grid[ iy + 1 ][ ix ];
                const d = grid[ iy + 1 ][ ix + 1 ];
                if ( iy !== 0  ) indices.push( a, b, d );
                if ( iy !== total) indices.push( b, c, d );
            }
        }

        this.setIndex( indices );
        this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
        this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
        this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
    }

    static fromJSON( data ) {
        return new CapsuleBufferGeometry( data.radius,data.height,data.widthSegments,data.endSegments,data.bodySegments,data.phiStart,data.phiLength);
    }

}